package org.jboss.seam.ui.validator; import javax.el.ELException; import javax.el.ValueExpression; import javax.faces.FacesException; import javax.faces.application.Application; import javax.faces.application.FacesMessage; import javax.faces.component.EditableValueHolder; import javax.faces.component.NamingContainer; import javax.faces.component.StateHolder; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.render.Renderer; import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.ui.component.UIDecorate; /** * Validate two fields are equal * * @author pmuir * @author Daniel Roth * */ public class EqualityValidator implements Validator, StateHolder { private static LogProvider log = Logging.getLogProvider(EqualityValidator.class); public static final String MESSAGE_ID = "org.jboss.seam.ui.validator.NOT_EQUAL"; public static final String VALIDATOR_ID = "org.jboss.seam.ui.validator.Equality"; private enum ValidOperation { EQUAL, NOT_EQUAL, GREATER, GREATER_OR_EQUAL, LESS, LESS_OR_EQUAL; } private String forId; private String message; private String messageId; private ValidOperation operator = ValidOperation.EQUAL; // Default public EqualityValidator() { this.message = "Value does not equal that in '#0'"; this.messageId = MESSAGE_ID; } public EqualityValidator(String forId) { this(); setFor(forId); } public EqualityValidator(String forId, String message, String messageId, String operator) { this(forId); if (message != null) { setMessage(message); } if (messageId != null) { setMessageId(messageId); } if (operator != null && !"".equals(operator)) { if (ValidOperation.valueOf(operator.toUpperCase()) != null) setOperator(ValidOperation.valueOf(operator.toUpperCase())); else throw new IllegalStateException("Illegal operator. " + "Supported are: " + validOperatorsAsString()); } } private String validOperatorsAsString() { StringBuffer buff = new StringBuffer(); for (ValidOperation op : ValidOperation.values()) { buff.append(op.name()).append(" "); } return buff.toString(); } public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException { if (getFor() == null) { throw new FacesException("Must specify a component to validate equality against"); } UIComponent otherComponent = findOtherComponent(component); Object other = new OtherComponent(context, otherComponent).getValue(); if (value == null && other == null) { // Thats fine } else if (value != null) { switch (operator) { case EQUAL: if (!value.equals(other)) { throwValidationException(value, otherComponent, other); } break; case NOT_EQUAL: if (value.equals(other)) { throwValidationException(value, otherComponent, other); } break; case GREATER: if (!(compare(value, other) > 0)) { throwValidationException(value, otherComponent, other); } break; case GREATER_OR_EQUAL: if (!(compare(value, other) >= 0)) { throwValidationException(value, otherComponent, other); } break; case LESS: if (!(compare(value, other) < 0)) { throwValidationException(value, otherComponent, other); } break; case LESS_OR_EQUAL: if (!(compare(value, other) <= 0)) { throwValidationException(value, otherComponent, other); } break; } } } private UIComponent findOtherComponent(UIComponent component) { UIComponent otherComponent = component.findComponent(getFor()); /** * If s:decorate is used, otherComponent will be null We have to look it * up ourselves */ if (otherComponent == null) { UIComponent decorateParent = null; UIComponent parent = component.getParent(); while (decorateParent == null && parent != null) { if (parent instanceof NamingContainer && !(parent instanceof UIDecorate)) { decorateParent = parent; } parent = parent.getParent(); } if (decorateParent != null) otherComponent = findChildComponent(decorateParent); } return otherComponent; } private UIComponent findChildComponent(UIComponent parent) { UIComponent ret = null; for (UIComponent child : parent.getChildren()) { if (child.getId().equals(getFor())) ret = child; else ret = findChildComponent(child); if (ret != null) break; } return ret; } private int compare(Object value, Object other) throws IllegalArgumentException { try { Comparable c1 = (Comparable) value; return c1.compareTo(other); } catch (Exception e) { throw new IllegalArgumentException("Values are not comparable", e); } } private void throwValidationException(Object value, UIComponent otherComponent, Object other) { throw new ValidatorException(FacesMessages.createFacesMessage(FacesMessage.SEVERITY_ERROR, getMessageId(), getMessage(), otherComponent.getId(), value, other)); } public String getFor() { return forId; } public void setFor(String forId) { this.forId = forId; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getMessageId() { return messageId; } public void setMessageId(String messageId) { this.messageId = messageId; } public boolean isTransient() { return false; } public void restoreState(FacesContext context, Object state) { Object[] fields = (Object[]) state; forId = (String) fields[0]; message = (String) fields[1]; messageId = (String) fields[2]; operator = ValidOperation.valueOf((String) fields[3]); } public Object saveState(FacesContext context) { Object[] state = new Object[4]; state[0] = forId; state[1] = message; state[2] = messageId; state[3] = operator.toString(); return state; } public void setTransient(boolean newTransientValue) { // No-op } /** * Simple data structure to hold info on the "other" component * * @author pmuir * */ private class OtherComponent { private FacesContext context; private UIComponent component; private EditableValueHolder editableValueHolder; private Renderer renderer; private Converter converter; public OtherComponent(FacesContext facesContext, UIComponent component) { this.component = component; this.context = facesContext; if (!(component instanceof EditableValueHolder)) { throw new IllegalStateException("forId must reference an EditableValueHolder (\"input\") component"); } editableValueHolder = (EditableValueHolder) component; initRenderer(); initConverter(); } private void initRenderer() { if (renderer == null) { String rendererType = component.getRendererType(); if (rendererType != null) { renderer = context.getRenderKit().getRenderer(component.getFamily(), rendererType); if (null == renderer) { log.trace("Can't get Renderer for type " + rendererType); } } else { if (log.isTraceEnabled()) { String id = component.getId(); id = (null != id) ? id : component.getClass().getName(); log.trace("No renderer-type for component " + id); } } } } private void initConverter() { converter = editableValueHolder.getConverter(); if (converter != null) { return; } ValueExpression valueExpression = component.getValueExpression("value"); if (valueExpression == null) { return; } Class converterType; try { converterType = valueExpression.getType(context.getELContext()); } catch (ELException e) { throw new FacesException(e); } // if converterType is null, String, or Object, assume // no conversion is needed if (converterType == null || converterType == String.class || converterType == Object.class) { return; } // if getType returns a type for which we support a default // conversion, acquire an appropriate converter instance. try { Application application = context.getApplication(); converter = application.createConverter(converterType); } catch (Exception e) { throw new FacesException(e); } } private Object getConvertedValue(Object newSubmittedValue) throws ConverterException { Object newValue; if (renderer != null) { newValue = renderer.getConvertedValue(context, component, newSubmittedValue); } else if (newSubmittedValue instanceof String) { // If there's no Renderer, and we've got a String, run it // through // the Converter (if any) if (converter != null) { newValue = converter.getAsObject(context, component, (String) newSubmittedValue); } else { newValue = newSubmittedValue; } } else { newValue = newSubmittedValue; } return newValue; } public Object getValue() { /** * If conversion already is done, return value */ if (editableValueHolder.isLocalValueSet()) { return editableValueHolder.getValue(); } /** * Convert submittet value */ Object submittedValue = editableValueHolder.getLocalValue(); if (submittedValue == null) { return null; } Object newValue = null; try { newValue = getConvertedValue(submittedValue); } catch (ConverterException ce) { // Any errors will be attached by JSF return null; } return newValue; } } public ValidOperation getOperator() { return operator; } public void setOperator(ValidOperation operator) { this.operator = operator; } }